package controllers

import (
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/orm"
	"github.com/robfig/cron"
	"io/ioutil"
	"log"
	"os"
	"reflect"
	"sync"
	"time"
)

type JobCron struct {
	jobCtl  TblJobCtl
	entryID cron.EntryID
}

type JobCronController struct {
	lock       sync.Mutex
	crontab    *cron.Cron
	jobCronMap map[string]*JobCron
}

var (
	jobController *JobCronController
)

func init() {
	jobController = new(JobCronController)

	cronOutput := ioutil.Discard
	cronLogFile := beego.AppConfig.String("cronLogOutputFile")

	if len(cronLogFile) > 0 {
		cronOutput, _ = os.Create(cronLogFile)
	}

	jobController.crontab = cron.New(cron.WithSeconds(),
		cron.WithLogger(cron.VerbosePrintfLogger(log.New(cronOutput, "cron: ", log.LstdFlags|log.Lmicroseconds))))
	jobController.jobCronMap = make(map[string]*JobCron, 10)

	jobController.crontab.Start()
}

func JobInit() error {
	go func() {
		interval, _ := beego.AppConfig.Int("intervalScan")

		if interval < 5 {
			interval = 5
		}

		for range time.Tick(time.Duration(interval) * time.Second) {
			JobLoop()
		}
	}()

	return nil
}

func JobLoop() {
	var (
		err    error
		o      = orm.NewOrm()
		jobCtl []TblJobCtl
	)

	if _, err = o.QueryTable("tbl_job_ctl").All(&jobCtl); err != nil {
		if err == orm.ErrNoRows {
			return
		}

		panic(err)
	}

	for _, row := range jobCtl {
		if m, ok := jobController.jobCronMap[row.JobId]; ok {
			if row == m.jobCtl {
				continue
			}

			beego.Info(row.JobId, row.JobName, row.Cron, row.Command, "config changed!")

			jobController.crontab.Remove(m.entryID)
			delete(jobController.jobCronMap, row.JobId)
		}

		if row.Valid == JOB_VALID_OFF {
			beego.Debug(row.JobId, row.JobName, row.Cron, row.Command, "config disabled.")
			continue
		}

		jobCron := new(JobCron)
		jobCron.jobCtl = row

		beego.Info(row.JobId, row.JobName, row.Cron, row.Command, "add job...")

		jobCron.entryID, err = jobController.crontab.AddJob(row.Cron, row)
		if err != nil {
			beego.Error(row.JobId, row.JobName, row.Cron, row.Command, err)
			continue
		}

		jobController.jobCronMap[row.JobId] = jobCron
	}
}

func (jobCtl TblJobCtl) Run() {
	jobDtl := new(TblJobDtl)

	mainType := reflect.TypeOf(jobCtl)
	mainValue := reflect.ValueOf(jobCtl)
	dtlEle := reflect.ValueOf(jobDtl).Elem()

	for i := 0; i < mainType.NumField(); i++ {
		mainKey := mainType.Field(i)
		mainValue := mainValue.Field(i)
		dtlValue := dtlEle.FieldByName(mainKey.Name)

		if !dtlValue.IsValid() {
			continue
		}

		switch mainValue.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			dtlValue.SetInt(mainValue.Int())
		case reflect.String:
			dtlValue.SetString(mainValue.String())
		default:
		}
	}

	jobDtl.Run()
}
